继承关系
1 | public class ArrayList<E> extends AbstractList<E> |
ArrayList是开发者广泛使用的java集合,它继承自AbstractList 实现了List接口,支持随机访问以及拷贝,持久化。
本篇主要对ArrayList的实现进行分析,首先我们看它的构造方法
1 | public ArrayList(int initialCapacity) { |
ArrayList的构造方法很简单,主要是初始化elementData值,它是一个Object数组用来存储我们的元素。根据参数的不同实现也不同。
元素添加
1 | public boolean add(E e) { |
首先调用ensureCapacityInternal来确保有足够的容量来存储e,随后将e保存在elementData数组中并增加size值 默认返回true。
在指定索引处添加元素
1 | public void add(int index, E element) { |
这个方法将元素element添加到数组中指定的index位置,所以效率上比add要慢,因为可能需要对数组元素进行挪动。同样这里需要保证足够的空间,并在添加完成后增加size计数。
对于Add方法,这里需要注意的是当elemmentData数组为空时,如果执行add(1,E)时会抛出IndexOutOfBoundsException这是因为在Add之前通过rangeCheckForAdd检查了index的值是否在[0,size]的区间,如果不再该区间那么抛出该异常。
1 | private void rangeCheckForAdd(int index) { |
元素删除
元素的删除有两个版本,分别针对索引和元素进行删除
1 | public E remove(int index) { |
通过索引删除,需要先对index做检测,删除index必须小于size,否则会报IndexOutOfBoundsException,然后从数组中取出index对应的元素oldValue,作为结果返回,随后计算需要移动的元素个数,然后通过System.arracopy完成index后面元素的移动。
1 | public boolean remove(Object o) { |
通过元素删除首先判断要删除的元素o是否为null,如果为null,遍历当前数组找到对应的索引然后通过fastRemove删除,如果o不为null,则通过其equals方法在数组中找到对应的元素索引,然后通过fastRemove删除。
1 | private void fastRemove(int index) { |
这里有个问题:可以用for循环直接删除ArrayList的特定元素?
答案是不可以,不同的for循环删除可能错误都不同,如果时一般的for循环则只能删除一个元素,而如果使用泛型for删除的话会抛出ConcurrentModificationException,使用泛型for会用到List内部的迭代器Itr,它在通过next遍历元素时会通过checkForComodification检查ArrayList的modCount和expectedModCount是否一直,不一致的话就会抛出ConcurrentModificationException异常。
1 | ArrayList<String> arr = new ArrayList<String>(); |
这种删除方式在remove方法中删除元素会更新modCount计数,而expectedModCount只会在迭代器版本的remove方法中更新,所以导致了不一致的情况,正确的方法是使用list.iterator()对ArrayList循环删除。
扩容
我们看看ensureCapacityInternal是如何保证容量大小的,即ArrayList如何进行扩容的
1 | private void ensureCapacityInternal(int minCapacity) { |
DEFAULT_CAPACITY默认为10,也就是说第一次添加到ArrayList的时候会开辟大小为10的数组。如果需要的大小大于当先数组的大小需要调用grow来完成容量的增加。
grow的resize逻辑如下:
- 计算新的容量大小为 int newCapacity = oldCapacity + (oldCapacity >> 1)这个新的容量为原来的1.5倍。
- 如果新的容量小于我们需要的 则以我们需要的为准开辟空间
- 如果新的容量大于最大可以开辟的容量值(Integer.MAX_VALUE - 8),那么需要hugeCapacity进行大容量的分配
- 使用Arrays的copyOf将原数组resize到指定大小